﻿using System;
using System.ServiceModel;
using System.Collections.Generic;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using SSSToolbox;
using System.Text;

namespace CRMToolbox
{
    public class Profiler : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            ITracingService tracingService =
                (ITracingService)serviceProvider.GetService(typeof(ITracingService));

            IPluginExecutionContext context = (IPluginExecutionContext)
                serviceProvider.GetService(typeof(IPluginExecutionContext));

            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

            //Obtain the global log. If one is not found, create it.
            LogHandler logHandler = new LogHandler();
            Entity GlobalLog = logHandler.GetGlobalLog(service);

            //Create / Update
            if (context.InputParameters.Contains("Target") &&
                context.InputParameters["Target"] is Entity)
            {
                Entity entity = (Entity)context.InputParameters["Target"];

                if (entity.LogicalName != "exchangesyncidmapping")
                {
                    return;
                }
                RunStep(entity, service, context, tracingService, GlobalLog, logHandler);
            }

            //Delete
            if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is EntityReference)
            {
                EntityReference entity = (EntityReference)context.InputParameters["Target"];
                if (entity.LogicalName == "exchangesyncidmapping" && context.MessageName == "Delete")
                {
                    try
                    {
                        Entity mapping = ConvertToEntity(entity, service, context);
                        RunStep(mapping, service, context, tracingService, GlobalLog, logHandler);
                    }
                    catch (Exception e)
                    {
                        logHandler.LogError(GlobalLog, service, logHandler.BuildErrorString(e));
                        return;
                    }
                }
            }
            return;
        }

        private void RunStep(Entity entity, IOrganizationService service, IPluginExecutionContext context, ITracingService tracingService, Entity gblLog, LogHandler logHandler)
        {
            try
            {
                string message = context.MessageName.ToString();

                //Determine the Owner of the mapping record (synchronizing user)
                EntityReference ownerid = GetOwner(entity, service);
                //Get the mailbox associated with the synchronizing user.
                EntityCollection mailboxes = GetMailboxes(service, ownerid);

                if (mailboxes.Entities.Count > 1)
                {
                    //To-Do: We should prepare this message with StringBuilder. 
                    //However, this may not be an issue since the thread and objects on the stack will be destroyed when plugin completes...
                    logHandler.LogError(gblLog, service, DateTime.Now.ToString() + ": " + "Found more than one mailbox with RegardingId " + ownerid.Id.ToString() + " - Aborting profiler");
                    return;
                }

                Entity mailbox = mailboxes[0];

                //Check to see if there is an active profiler for this mailbox.
                EntityCollection Profilers = GetProfilerRecord(service, mailbox.Id);

                if (Profilers.Entities.Count != 1)
                {
                    logHandler.LogError(gblLog, service, DateTime.Now.ToString() + ": " + "Found more than one active profiler record for MailboxId" + mailbox.Id + " - Aborting profiler");
                    return;
                }

                Entity Profiler = Profilers[0];
                Guid ProfilerLogId = logHandler.GetProfilerLogId(service, Profiler);

                int tochangetype = GetToChangeType(entity);
                Guid objectid = GetObject(service, entity);
                int objecttypecode = GetObjectTypeCode(service, entity);

                //Check if filtering is enabled
                if ((bool)Profiler["crmtb_profilespecificrecords"])
                {
                    //Get Profiler Filter parameters
                    bool alloperations = (bool)Profiler["crmtb_alloperations"];
                    bool orphaneditems = (bool)Profiler["crmtb_orphanedrecords"];

                    //Check if the profiler record is profiling the current message or if we care about the params above.
                    if (!CheckMessage(Profiler, message, tochangetype, alloperations, orphaneditems, objectid, objecttypecode, service))
                    {
                        return;
                    }

                    //Get a collection of the specific records the user wants to filter on.
                    EntityCollection ProfilerFilterRecords = GetFilterRecords(service, Profiler);

                    //Convert the profiler filter collection's inner lookups to a dictionary, including any additional objects added through alloperations and orphaneditems. 
                    Dictionary<Guid, bool> records = BuildRecordList(ProfilerFilterRecords, alloperations, orphaneditems, objectid, objecttypecode, service, tochangetype);

                    //Check if the incoming ObjectId in the ExchangeSync record is in our dictionary.
                    if (!records.ContainsKey((Guid)entity["objectid"]))
                    {
                        return;
                    }

                    bool CanBeReferenced = records[objectid];
                    CreateProfileRow(entity, Profiler.Id, service, message, CanBeReferenced, objecttypecode, objectid);
                    return;
                }

                bool referenceableall = IsValidObject(service, objectid, objecttypecode);
                CreateProfileRow(entity, Profiler.Id, service, message, referenceableall, objecttypecode, objectid);

            }
            catch (FaultException<OrganizationServiceFault> ex)
            {

                tracingService.Trace("Profiler Plugin: {0}", ex.ToString());
                logHandler.LogError(gblLog, service, DateTime.Now.ToString() + ": " + ex.ToString());
                return;
            }

            catch (Exception ex)
            {
                tracingService.Trace("Profiler Plugin: {0}", ex.ToString());
                logHandler.LogError(gblLog, service, DateTime.Now.ToString() + ": " + ex.ToString());
                return;
            }
        }

        private Entity ConvertToEntity(EntityReference entity, IOrganizationService service, IPluginExecutionContext context)
        {
            ColumnSet columns;

            if (context.InputParameters.Contains("ColumnSet"))
            {
                columns = (ColumnSet)context.InputParameters["ColumnSet"];
            }
            else
            {
                columns = new ColumnSet("objectid");
            }

            Entity mapping = service.Retrieve("exchangesyncidmapping", entity.Id, columns);
            return mapping;
        }

        private int GetToChangeType(Entity entity)
        {
            if (entity.Attributes.ContainsKey("tocrmchangetype"))
            {
                int tochangetype = (int)entity["tocrmchangetype"];
                return tochangetype;
            }
            else
            {
                return 0;
            }
        }

        private int GetObjectTypeCode(IOrganizationService service, Entity entity)
        {
            if (entity.Attributes.ContainsKey("objecttypecode"))
            {
                int objecttypecode = (int)entity["objecttypecode"];
                return objecttypecode;
            }
            else
            {
                int determinedobjecttypecode = DetermineOtc(service, (Guid)entity["exchangesyncidmappingid"]);
                return determinedobjecttypecode;
            }
        }

        private Guid GetObject(IOrganizationService service, Entity entity)
        {
            if (entity.Attributes.ContainsKey("objectid"))
            {
                Guid objectid = (Guid)entity["objectid"];
                return objectid;
            }
            else
            {
                Guid determinedobjectid = DetermineObjectId(service, (Guid)entity["exchangesyncidmappingid"]);
                return determinedobjectid;
            }
        }

        private EntityReference GetOwner(Entity entity, IOrganizationService service)
        {
            if (!entity.Attributes.ContainsKey("ownerid"))
            {
                EntityReference determinedownerid = DetermineOwner(service, entity.Id);
                return determinedownerid;
            }
            else
            {
                EntityReference ownerid = (EntityReference)entity["ownerid"];
                return ownerid;
            }
        }

        private bool IsFalseRetrieve(Entity entity)
        {
            if (entity.Attributes.ContainsKey("objecttypecode") && entity.Attributes.ContainsKey("exchangesyncidmappingid") && entity.Attributes.Count == 2)
            {
                return true;
            }

            if (entity.Attributes.ContainsKey("objectid") && entity.Attributes.ContainsKey("exchangesyncidmappingid") && entity.Attributes.Count == 2)
            {
                return true;
            }

            if (entity.Attributes.ContainsKey("ownerid") && entity.Attributes.ContainsKey("exchangesyncidmappingid") && entity.Attributes.Count == 2)
            {
                return true;
            }
            return false;
        }

        private bool CheckMessage(Entity profiler, string message, int tocrmchangetype, bool createevents, bool orphaneditems, Guid objectid, int otc, IOrganizationService service)
        {
            bool filtercreate = (bool)profiler["crmtb_filtercreate"];
            bool filterupdate = (bool)profiler["crmtb_filterupdate"];
            //bool filterretrieve = (bool)profiler["crmtb_filterretrieve"];
            bool filterdelete = (bool)profiler["crmtb_filterdelete"];

            if (message == "Create" && filtercreate)
            {
                return true;
            }

            if (message == "Update" && filterupdate)
            {
                return true;
            }

            if (message == "Delete" && filterdelete)
            {
                return true;
            }

            if (createevents && tocrmchangetype == 1 || tocrmchangetype == 6)
            {
                return true;
            }

            if (orphaneditems && !IsValidObject(service, objectid, otc))
            {
                return true;
            }

            return false;
        }

        private int DetermineOtc(IOrganizationService service, Guid guid)
        {
            Entity exchangemapping;

            try
            {
                exchangemapping = service.Retrieve("exchangesyncidmapping", guid, new ColumnSet("objecttypecode"));
            }
            catch
            {
                int EmptyOtc = 0;
                return EmptyOtc;
            }

            int otc = (int)exchangemapping["objecttypecode"];
            return otc;
        }

        private Guid DetermineObjectId(IOrganizationService service, Guid guid)
        {

            Entity exchangemapping;

            try
            {
                exchangemapping = service.Retrieve("exchangesyncidmapping", guid, new ColumnSet("objectid"));
            }

            catch
            {
                Guid EmptyObject = Guid.Empty;
                return EmptyObject;
            }

            Guid objectid = (Guid)exchangemapping["objectid"];
            return objectid;
        }

        private EntityReference DetermineOwner(IOrganizationService service, Guid id)
        {
            Entity exchangemapping = service.Retrieve("exchangesyncidmapping", id, new ColumnSet("ownerid"));
            EntityReference ownerid = (EntityReference)exchangemapping["ownerid"];
            return ownerid;

        }

        private void CreateProfileRow(Entity mapping, Guid profilerid, IOrganizationService service, string message, bool referenceable, int objecttypecode, Guid objectid)
        {
            Entity profilerrow = new Entity("crmtb_profilerdata");

            profilerrow["crmtb_profilerid"] = new EntityReference("crmtb_profiler", profilerid);

            if (message == "Update")
            {
                profilerrow["crmtb_mappingoperation"] = new OptionSetValue(589360001);
            }

            if (message == "Create")
            {
                profilerrow["crmtb_mappingoperation"] = new OptionSetValue(589360000);
            }

            if (message == "Retrieve")
            {
                profilerrow["crmtb_mappingoperation"] = new OptionSetValue(589360002);
            }

            if (message == "Delete")
            {
                profilerrow["crmtb_mappingoperation"] = new OptionSetValue(589360003);
            }

            if (mapping.Attributes.ContainsKey("exchangeentryid"))
            {
                profilerrow["crmtb_exchangeentryid"] = mapping["exchangeentryid"];
            }

            if (referenceable && objecttypecode > 0)
            {
                int otc = objecttypecode;

                switch (otc)
                {
                    case 2:
                        profilerrow["crmtb_objectidcontactid"] = new EntityReference("contact", objectid);
                        break;

                    case 4201:
                        profilerrow["crmtb_objectidappointmentid"] = new EntityReference("appointment", objectid);
                        break;

                    case 4204:
                        profilerrow["crmtb_objectidfaxid"] = new EntityReference("fax", objectid);
                        break;

                    case 4207:
                        profilerrow["crmtb_objectidletterid"] = new EntityReference("letter", objectid);
                        break;

                    case 4210:
                        profilerrow["crmtb_objectidphonecallid"] = new EntityReference("phonecall", objectid);
                        break;

                    case 4251:
                        profilerrow["crmtb_objectidrecurringappointmentmasterid"] = new EntityReference("recurringappointmentmaster", objectid);
                        break;

                    case 4212:
                        profilerrow["crmtb_objectidtaskid"] = new EntityReference("task", objectid);
                        break;
                }
            }

            if (!referenceable && (Guid)mapping["objectid"] != Guid.Empty)
            {
                profilerrow["crmtb_deletedobjectid"] = mapping["objectid"].ToString();
            }

            if (mapping.Attributes.ContainsKey("objecttypecode"))
            {
                profilerrow["crmtb_objecttypecode"] = new OptionSetValue(MapTypeCodes((int)mapping["objecttypecode"]));
            }

            if (mapping.Attributes.ContainsKey("fromcrmchangetype"))
            {
                profilerrow["crmtb_fromcrmchangetype"] = new OptionSetValue(MapChangeTypes((int)mapping["fromcrmchangetype"]));
            }

            if (mapping.Attributes.ContainsKey("isdeletedinexchange"))
            {
                if ((bool)mapping["isdeletedinexchange"] == true)
                {
                    profilerrow["crmtb_isdeletedinexchange"] = new OptionSetValue(589360000);
                }
                else
                {
                    profilerrow["crmtb_isdeletedinexchange"] = new OptionSetValue(589360001);
                }
            }

            if (mapping.Attributes.ContainsKey("isunlinkedincrm"))
            {
                if ((bool)mapping["isunlinkedincrm"] == true)
                {
                    profilerrow["crmtb_isunlinkedincrm"] = new OptionSetValue(589360000);
                }
                else
                {
                    profilerrow["crmtb_isunlinkedincrm"] = new OptionSetValue(589360001);
                }

            }

            if (mapping.Attributes.ContainsKey("lastsyncerror"))
            {
                profilerrow["crmtb_lastsyncerror"] = mapping["lastsyncerror"];
            }

            if (mapping.Attributes.ContainsKey("lastsyncerrorcode"))
            {
                profilerrow["crmtb_lastsyncerrorcode"] = (int)mapping["lastsyncerrorcode"];
            }

            if (mapping.Attributes.ContainsKey("lastsyncerroroccurredon"))
            {
                profilerrow["crmtb_lastsyncerroroccurredon"] = (DateTime)mapping["lastsyncerroroccurredon"];
            }

            if (mapping.Attributes.ContainsKey("exchangesyncidmappingid"))
            {
                string id = mapping.Id.ToString();
                profilerrow["crmtb_exchangesyncidmappingid"] = id;
            }

            if (mapping.Attributes.ContainsKey("ownerid"))
            {
                EntityReference owner = (EntityReference)mapping["ownerid"];
                profilerrow["crmtb_ownerid"] = new EntityReference("systemuser", owner.Id);
            }

            if (mapping.Attributes.ContainsKey("retries"))
            {
                profilerrow["crmtb_retries"] = (int)mapping["retries"];
            }

            if (mapping.Attributes.ContainsKey("tocrmchangetype"))
            {
                profilerrow["crmtb_tocrmchangetype"] = new OptionSetValue(MapChangeTypes((int)mapping["tocrmchangetype"]));
            }

            if (mapping.Attributes.ContainsKey("userdecision"))
            {
                profilerrow["crmtb_userdecision"] = (int)mapping["userdecision"];
            }

            if (mapping.Attributes.ContainsKey("versionnumber"))
            {
                profilerrow["crmtb_versionnumber"] = (string)mapping["versionnumber"];
            }

            profilerrow["crmtb_name"] = DateTime.Now.ToString();

            service.Create(profilerrow);
        }

        private bool IsValidObject(IOrganizationService service, Guid id, int objecttype)
        {
            otcenum ObjectTypeEnumerator = new otcenum();
            string logicalname = ObjectTypeEnumerator.GetStringType(objecttype);
            ObjectTypeEnumerator = null;

            if (id == Guid.Empty)
            {
                return false;
            }

            try
            {
                Entity record = service.Retrieve(logicalname, id, new ColumnSet());
            }

            catch
            {
                return false;
            }
            return true;
        }

        private int MapChangeTypes(int change)
        {
            int mappedchange = 0;

            switch (change)
            {
                case 0:
                    mappedchange = 589360000;
                    break;

                case 1:
                    mappedchange = 589360001;
                    break;

                case 2:
                    mappedchange = 589360002;
                    break;

                case 3:
                    mappedchange = 589360003;
                    break;

                case 4:
                    mappedchange = 589360004;
                    break;

                case 5:
                    mappedchange = 589360005;
                    break;

                case 6:
                    mappedchange = 589360006;
                    break;

                case 7:
                    mappedchange = 589360007;
                    break;
            }

            return mappedchange;
        }

        private int MapTypeCodes(int mapping)
        {
            int mappedvalue = 0;

            switch (mapping)
            {
                case 0:
                    mappedvalue = 0;
                    break;

                case 2:
                    mappedvalue = 589360000;
                    break;

                case 4201:
                    mappedvalue = 589360001;
                    break;

                case 4204:
                    mappedvalue = 589360002;
                    break;

                case 4207:
                    mappedvalue = 589360003;
                    break;

                case 4210:
                    mappedvalue = 589360004;
                    break;

                case 4251:
                    mappedvalue = 589360005;
                    break;

                case 4212:
                    mappedvalue = 589360006;
                    break;

            }
            return mappedvalue;
        }
        private EntityCollection GetMailboxes(IOrganizationService service, EntityReference ownerid)
        {
            EntityCollection Mailboxes;
            QueryByAttribute query = new QueryByAttribute("mailbox") { ColumnSet = new ColumnSet() };
            query.Attributes.AddRange("regardingobjectid");
            query.Values.AddRange(ownerid.Id);
            query.Attributes.AddRange("statecode");
            query.Values.AddRange(0);

            Mailboxes = service.RetrieveMultiple(query);

            return Mailboxes;

        }

        private EntityCollection GetProfilerRecord(IOrganizationService service, Guid mailbox)
        {
            int Active = 0;
            QueryByAttribute query = new QueryByAttribute("crmtb_profiler")
            {
                ColumnSet = new ColumnSet("crmtb_mailboxid", "crmtb_profilespecificrecords", "crmtb_orphanedrecords", "crmtb_alloperations", "crmtb_filtercreate", "crmtb_filterretrieve", "crmtb_filterupdate", "crmtb_filterdelete", "crmtb_enabledebuglogging", "crmtb_name")
            };
            query.Attributes.AddRange("statecode");
            query.Values.AddRange(Active);
            query.Attributes.AddRange("crmtb_mailboxid");
            query.Values.AddRange(mailbox);

            EntityCollection ProfilerRecords = service.RetrieveMultiple(query);

            return ProfilerRecords;
        }

        //This method retrieves the profiler filter records with the specified ACT items that the user wants to profile. 
        private EntityCollection GetFilterRecords(IOrganizationService service, Entity profiler)
        {
            int Active = 0;
            QueryByAttribute query = new QueryByAttribute("crmtb_profilerrecords")
            {
                ColumnSet = new ColumnSet("crmtb_contactid", "crmtb_appointmentid", "crmtb_faxid", "crmtb_letterid", "crmtb_phonecallid", "crmtb_recurringappointmentid", "crmtb_taskid")
            };
            query.Attributes.AddRange("statecode");
            query.Values.AddRange(Active);
            query.Attributes.AddRange("crmtb_profilerid");
            query.Values.AddRange(profiler.Id);

            EntityCollection ProfiledRecords = service.RetrieveMultiple(query);

            return ProfiledRecords;
        }

        private Dictionary<Guid, bool> BuildRecordList(EntityCollection ProfilerRecords, bool alloperations, bool orphaneditems, Guid id, int otc, IOrganizationService service, int tochangetype)
        {
            Dictionary<Guid, bool> values = new Dictionary<Guid, bool>();

            foreach (var record in ProfilerRecords.Entities)
            {

                if (record.Attributes.ContainsKey("crmtb_profilerrecordsid"))
                {
                    record.Attributes.Remove("crmtb_profilerrecordsid");
                }

                foreach (var attribute in record.Attributes)
                {
                    EntityReference lookup = (EntityReference)attribute.Value;
                    values.Add(lookup.Id, true);
                }
            }

            if (alloperations && id == Guid.Empty && tochangetype == 6 || tochangetype == 1)
            {
                values.Add(Guid.Empty, false);
            }

            if (orphaneditems)
            {
                if (!values.ContainsKey(id) && !IsValidObject(service, id, otc))
                {
                    values.Add(id, false);
                }
            }

            return values;
        }
    }
}
